/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.staf.NetworkReportGenerator.V2;

import java.util.HashMap;
import java.util.TreeMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;
import java.util.Collection;
import java.io.File;
import java.util.Comparator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.Date;
import java.text.Collator;

import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.OID;

import org.jrobin.data.DataProcessor;
import org.jrobin.core.RrdException;
import java.io.IOException;

import ru.staf.NetworkReportGenerator.SysUtil;

/**
 *
 * @author aezhov
 */
public class Switch implements Node, Comparator<String> {

    static final String fileSep = System.getProperty("file.separator");
    public static final String OID_SYS_DESCR = ".1.3.6.1.2.1.1.1";
    public static final String OID_IF_NAME = ".1.3.6.1.2.1.31.1.1.1.1";
    public static final String OID_IF_DESCR = ".1.3.6.1.2.1.2.2.1.2";
    public static final String OID_MAC_D = ".1.3.6.1.2.1.17.4.3.1.2";
    public static final String OID_MAC_Q = ".1.3.6.1.2.1.17.7.1.2.2.1.2";
    public static final String OID_IF_OPER_STATUS = ".1.3.6.1.2.1.2.2.1.8";
    public static final String OID_IF_SPEED = ".1.3.6.1.2.1.2.2.1.5";
    public static final String OID_DOTQ_VSTATENT = ".1.3.6.1.2.1.17.7.1.4.3.1";
    
    public static DownNode downNode = new DownNode();

    String name = null;
    String opennmsId = null;
    File jrobinDir = null;
    String portpattern = null;
    String snmpCommunity = null;
    File snmpDump = null;
    int snmpVersion = -1;
    String rootMac = null;
    Map<String, String> ifName = new HashMap<String, String>();
    Map<String, String> ifDescr = new HashMap<String, String>();
    Map<String, Set<String>> portMacMap = new HashMap<String, Set<String>>();
    Set<String> targetSet = new HashSet<String>();
    Map<String, Set<Switch>> switchRelations = new HashMap<String, Set<Switch>>();
    Map<String, Node> connections = new TreeMap<String, Node>(this);
    Set<String> upPorts = new HashSet<String>();
    Map<String, Integer> portSpeed = new HashMap<String, Integer>();
    Map<String, Set<Integer>> portVlanMap = new HashMap<String, Set<Integer>>();
    Pattern portPattern = null;
    DataProcessor jrProcessor = null;
    Date fromDate = null, toDate = null;
    String adminip = null;
    String sysDescr = "";

    public Switch(String configName, Date fromDate, Date toDate)
            throws RrdException {
        name = configName;
        this.fromDate = fromDate;
        this.toDate = toDate;
        jrProcessor = new DataProcessor(fromDate, toDate);
    }

    public void addConfigProperty(String qName, String value)
            throws ConfigurationException {
        String[] parts = qName.split("\\.");
        String p0 = null;

        if (parts.length > 1) {
            p0 = parts[0];
        } else {
            p0 = qName;
        }

        if (p0.equals("dir")) {
            jrobinDir = new File(value);
            if (!jrobinDir.isDirectory()) {
                jrobinDir = null;
            }

        } else if (p0.equals("portpattern")) {
            if (value == null || value.length() == 0) {
                throw new ConfigurationException(name + " empty portpattern defined");
            }
            portpattern = value;
            System.out.println("Switch.addConfigProperty: portpattern == "
                    + portpattern);
            portPattern = Pattern.compile(portpattern);

        } else if (p0.equals("adminip")) {
            if (value == null || value.length() == 0) {
                throw new ConfigurationException(name + " empty adminip");
            }
            adminip = value;
            System.out.println("Switch.addConfigProperty: adminip == " + adminip);
        } else if (p0.equals("snmp")) {
            String p1 = null;
            p1 = parts[1];
            if (p1.equals("community")) {
                if (value == null || value.length() == 0) {
                    throw new ConfigurationException(name + " empty snmp community defined");
                }
                snmpCommunity = value;

            } else if (p1.equals("version")) {
                if (value == null || value.length() == 0) {
                    throw new ConfigurationException(name + " snmp version");
                }
                if (value.equals("v1")) {
                    snmpVersion = SnmpConstants.version1;
                } else if (value.equals("v2c")) {
                    snmpVersion = SnmpConstants.version2c;
                }

            } else if (p1.equals("dump")) {
                System.out.println("Switch. Snmp dump file " + value);
                snmpDump = new File(value);
                if (!snmpDump.exists()) {
                    throw new ConfigurationException(name + " dumpfile " + value + "doesnt exist");
                }
            }
        }
    }

    public boolean commitConfiguration(String monStatMAC) throws IOException {
        boolean isRoot = false;
        rootMac = monStatMAC;

        SnmpWorker snmp = null;
        if (snmpDump != null) {
            snmp = new FileSnmpWorker(snmpDump);
        } else {
            snmp = new NetworkSnmpWorker(adminip);
        }

        snmp.setSnmpVersion(snmpVersion);
        snmp.setSnmpCommunity(snmpCommunity);

        try {
            Set<VariableBinding> ifNames = snmp.snmpWalk(OID_IF_NAME);
            Set<VariableBinding> ifDescrs = snmp.snmpWalk(OID_IF_DESCR);
            Set<VariableBinding> qBridgeStatic = snmp.snmpWalk(OID_DOTQ_VSTATENT + ".2");
            Set<VariableBinding> qBridgeUntagged = snmp.snmpWalk(OID_DOTQ_VSTATENT + ".4");
            Set<VariableBinding> sysDescrs = snmp.snmpWalk(OID_SYS_DESCR);

            for (VariableBinding vb : sysDescrs) {
                OctetString var = (OctetString)vb.getVariable();
                sysDescr = var.toString();
                System.out.println("++++++ sysDescr " + sysDescr);
            }

            Iterator<VariableBinding> it = ifNames.iterator();
            while (it.hasNext()) {
                VariableBinding vb = it.next();
                OID oid = vb.getOid();
                int port = oid.get(11);
                String name = vb.getVariable().toString();
                String[] parts = name.split("\"");
                if (parts.length == 1) {
                    name = parts[0].trim();
                } else {
                    name = parts[1].trim();
                }

                name = name.replaceAll("[^A-Za-z0-9]", "_");
                if (name.length() > 0) {
                    Matcher matcher = portPattern.matcher(name);
                    if (matcher.matches()) {
                        connections.put(name, downNode);
                        try {
                            addDataSources(name);
                            SysUtil.usedMemory("after addDataSources");
                        } catch (IOException exc) {
                            System.out.println("Switch.commitConfiguration: "
                                    + "IOException " + exc.toString());
                        }

                        ifName.put(new Integer(port).toString(), name);
                    }
                }
                //ifName.put(new Integer(port).toString(), name);
            }

            it = ifDescrs.iterator();
            while (it.hasNext()) {
                VariableBinding vb = it.next();
                OID oid = vb.getOid();
                int port = oid.get(10);
                String descr = vb.getVariable().toString();
                String[] parts = descr.split("\"");
                if (parts.length == 1) {
                    descr = parts[0].trim();
                } else {
                    descr = parts[1].trim();
                }
                descr = descr.replaceAll("[^A-Za-z0-9]", "_");

                if (descr.length() > 0) {
                    Matcher matcher = portPattern.matcher(descr);
                    if (matcher.matches()) {
                        connections.put(descr, downNode);
                        try {
                            addDataSources(descr);
                            SysUtil.usedMemory("after addDataSources");
                        } catch (IOException exc) {
                            System.out.println("Switch.commitConfiguration: "
                                    + "IOException " + exc.toString());
                        }

                        ifDescr.put(new Integer(port).toString(), descr);
                    }
                }
                //ifDescr.put(new Integer(port).toString(), descr);
            }

            Set<VariableBinding> ifMacQ = snmp.snmpWalk(OID_MAC_Q);
            Set<VariableBinding> ifMacD = snmp.snmpWalk(OID_MAC_D);

            SysUtil.usedMemory("after MAC_Q and MAC_D snmpwalk");
            
            it = ifMacQ.iterator();
            while (it.hasNext()) {
                VariableBinding vb = it.next();
                OID oid = vb.getOid();
                int vid = oid.get(13);
                String mac = String.format("%02X:%02X:%02X:%02X:%02X:%02X",
                        oid.get(14), oid.get(15), oid.get(16), oid.get(17), oid.get(18), oid.get(19));
                String port = vb.getVariable().toString();
                Set<String> macs = portMacMap.get(port);
                if (macs == null) {
                    macs = new HashSet<String>();
                    portMacMap.put(port, macs);
                }
                macs.add(mac);

                // Adding vlan info
                Map<String, String> actualMap = null;
                if (ifName.containsKey(port))
                    actualMap = ifName;
                else if (ifDescr.containsKey(port))
                    actualMap = ifDescr;
                else
                    continue;

                String portName = actualMap.get(port);
                Set<Integer> vlans = portVlanMap.get(portName);
                if (vlans == null) {
                    vlans = new TreeSet<Integer>();
                    portVlanMap.put(portName, vlans);
                }
                vlans.add(new Integer(vid));
            }

            it = ifMacD.iterator();
            while (it.hasNext()) {
                VariableBinding vb = it.next();
                OID oid = vb.getOid();
                String mac = String.format("%02X:%02X:%02X:%02X:%02X:%02X",
                        oid.get(11), oid.get(12), oid.get(13), oid.get(14), oid.get(15), oid.get(16));
                String port = vb.getVariable().toString();
                Set<String> macs = portMacMap.get(port);
                if (macs == null) {
                    macs = new HashSet<String>();
                    portMacMap.put(port, macs);
                }

                macs.add(mac);
            }

            SysUtil.usedMemory("after MAC iterations");
            Set<VariableBinding> speeds = snmp.snmpWalk(OID_IF_SPEED);
            SysUtil.usedMemory("S6");
            for (VariableBinding vb : speeds) {
                OID oid = vb.getOid();
                int port = oid.get(10);
                String stat = vb.getVariable().toString();

                Integer portNum = new Integer(port);
                String pName = ifName.get(portNum.toString());
                if (pName == null) {
                    pName = ifDescr.get(portNum.toString());
                }
                if (pName != null) {
                    System.out.println("OID_IF_SPEED port " + pName
                            + " value " + stat);
                    portSpeed.put(pName, new Integer(stat));
                }
            }

            SysUtil.usedMemory("after Speed iterations");

            Set<VariableBinding> states = snmp.snmpWalk(OID_IF_OPER_STATUS);
            for (VariableBinding vb : states) {
                OID oid = vb.getOid();
                int port = oid.get(10);
                String stat = vb.getVariable().toString();
                String[] parts = stat.split("[()]");
                if (parts.length > 1) {
                    stat = parts[1];
                } else {
                    stat = parts[0];
                }

                if (stat.equals("1")) {            // Up
                    Integer portNum = new Integer(port);
                    String pName = ifName.get(portNum.toString());
                    if (pName == null) {
                        pName = ifDescr.get(portNum.toString());
                    }
                    if (pName != null) {
                        System.out.println("UPDOWN " + name + "." + pName + " state: UP");
                        upPorts.add(pName);
                    }
                }
            }

            SysUtil.usedMemory("after ifUp iterations");
            
            // Get port-vlans mappings
            for (VariableBinding vb : qBridgeStatic) {
                OID oid = vb.getOid();
                int vid = oid.get(13);
                OctetString os = (OctetString) vb.getVariable();
                byte[] bytes = os.getValue();

                int pnum = 0;
                int worker = 0;
                Map<String, String> actMap = null;

                for (byte abyte : bytes) {
                    for (int mask = 0x80; mask > 0; mask >>= 1) {
                        pnum++;
                        if ((abyte & mask) == mask) {
                            String pnumStr = new Integer(pnum).toString();
                            if (ifName.containsKey(pnumStr)) {
                                actMap = ifName;
                            } else if (ifDescr.containsKey(pnumStr)) {
                                actMap = ifDescr;
                            } else {
                                continue;
                            }

                            String pName = actMap.get(pnumStr);
                            Integer vidObj = new Integer(vid);
                            Set<Integer> vlans = portVlanMap.get(pName);
                            if (vlans == null) {
                                vlans = new HashSet<Integer>();
                                portVlanMap.put(pName, vlans);
                            }
                            vlans.add(vidObj);
                        }
                    }
                }
            }

            for (VariableBinding vb : qBridgeUntagged) {
                OID oid = vb.getOid();
                int vid = oid.get(13);
                OctetString os = (OctetString) vb.getVariable();
                byte[] bytes = os.getValue();
            }

            SysUtil.usedMemory("after QBRIDGE iterations");
            
        } catch (SnmpException exc) {
            System.out.println("Switch. " + name + " SnmpException " + exc.toString());
        }

        Collection<Set<String>> macs = portMacMap.values();
        Iterator<Set<String>> macIt = macs.iterator();
        while (macIt.hasNext()) {
            Set<String> macSet = macIt.next();
            if (macSet.size() == 1) {
                String mac = macSet.iterator().next();
                targetSet.add(mac);
                if (mac.equals(monStatMAC)) {
                    System.out.println("Switch.commitConfiguration: " + name
                            + " is a ROOT SWITCH");
                    isRoot = true;
                }
            }
        }

        System.out.println("Swich.commitConfiguration: " + name + " target "
                + "set size == " + targetSet.size());

        SysUtil.usedMemory("end of commitConfiguration");
        return isRoot;
    }

    
    public void calculatePathes(Collection<Switch> switches) {
        Iterator<String> portIt = portMacMap.keySet().iterator();
        while (portIt.hasNext()) {
            String port = portIt.next();
            Set<String> macs = portMacMap.get(port);
            if (macs.size() > 1) {
                Iterator<Switch> swIt = switches.iterator();
                while (swIt.hasNext()) {
                    Switch sw = swIt.next();
                    if (sw == this) {
                        System.out.println("calculateDeps: " + name + " skipping "
                                + sw.getName());
                        continue;
                    }

                    /*System.out.println("### " + name + "." + port + "macs: " +
                    macs);
                    System.out.println("### " + sw + " targets: " + sw.getName() + " " + sw.targetSet);*/
                    if (sw.areYouInList(macs)) {
                        System.out.println("calculateDeps: sw "
                                + name + " port " + port + " related with "
                                + sw.getName());
                        Set<Switch> rswSet = switchRelations.get(port);
                        if (rswSet == null) {
                            rswSet = new HashSet<Switch>();
                            switchRelations.put(port, rswSet);
                        }
                        rswSet.add(sw);
                    }
                }
            }
        }
    }

    public void recalculatePathes() {
        for (String port : switchRelations.keySet()) {
            Set<Switch> swSet = switchRelations.get(port);
            if (swSet.size() > 1 && !swSet.contains(ConfigParser.rootSwitch)) {
                System.out.println("=== Recalculating pathes for "
                        + name + "." + port + " set:" + getPathString(swSet));

                Set<Switch> origSet = new HashSet<Switch>(swSet);

                Switch nearest = null;

                while (true) {
                    for (Switch sw : swSet) {
                        //System.out.println("    " + sw.getName());
                        Set<Switch> result = sw.recalculatePathes(swSet);
                        //System.out.println("    intersection: "
                        //        + getPathString(result));
                        if (result.isEmpty()) {
                            origSet.remove(sw);
                        }

                        if (origSet.size() == 1) {
                            nearest = origSet.iterator().next();
                            break;
                        }

                        //System.out.println("    SwSet: " + getPathString(swSet));
                        //System.out.println("    OrigSet: " + getPathString(origSet));
                    }

                    if (nearest != null) {
                        System.out.println("    CONNECTED TO " + nearest.getName());
                        switchRelations.put(port, origSet);
                        break;
                    } else {
                        swSet = new HashSet<Switch>(origSet);
                    }
                }
            }
        }
    }

    public Map<String, Switch> constructConnections() throws RrdException,
            IOException {
        Map<String, Switch> autoSwitches = new HashMap<String, Switch>();

        for (String port : portMacMap.keySet()) {
            Set<String> macs = portMacMap.get(port);
            if (macs != null && macs.size() == 1) {
                String portName = ifName.get(port);
                if (portName == null || portName.length() == 0) {
                    portName = ifDescr.get(port);
                }


                if (portName != null) {
                    Node node = connections.get(portName);
                    System.out.println("+++ SW" + name + " port " + port + " node == " + node);
                    if (node != null) {
                        //node = new LeafDevice(macs.iterator().next());
                        node = LeafDevice.getConcreteDevice(macs.iterator().next());
                        connections.put(portName, node);
                    }
                }
            } else if (macs != null & macs.size() > 1) {
                Set<Switch> swSet = switchRelations.get(port);
                if (swSet == null) {
                    AutoSwitch as = new AutoSwitch(this, fromDate, toDate);
                    as.addAllMacs(macs);
                    HashSet<Switch> aSet = new HashSet<Switch>();
                    aSet.add(as);
                    autoSwitches.put(as.getName(), as);
                    switchRelations.put(port, aSet);
                }
            }
        }


        for (String port : switchRelations.keySet()) {
            Set<Switch> swSet = switchRelations.get(port);
            if (swSet != null && swSet.size() == 1) {
                String portName = ifName.get(port);
                if (portName == null || portName.length() == 0) {
                    portName = ifDescr.get(port);
                }

                if (portName != null) {
                    connections.put(portName, swSet.iterator().next());
                }
            }
        }

        for (String pname : upPorts) {
            System.out.println("PORTS UP " + name + "." + pname);
        }

        jrProcessor.processData();

        return autoSwitches;
    }

    public void setReverseLinks() {
        for (String port : switchRelations.keySet()) {
            Set<Switch> swSet = switchRelations.get(port);
            if (swSet.size() == 1) {
                Switch sw = swSet.iterator().next();
                sw.setNeighbour(this);
            }
        }
    }

    public void setNeighbour(Switch neighbour) {
        for (Set<Switch> swSet : switchRelations.values()) {
            if (swSet.contains(neighbour) || swSet.contains(ConfigParser.rootSwitch)) {
                swSet.clear();
                swSet.add(neighbour);
            }
        }
    }

    public Set<Switch> recalculatePathes(Set<Switch> swSet) {
        Set<Switch> intersection = new HashSet<Switch>();

        for (String port : switchRelations.keySet()) {
            Set<Switch> mySet = switchRelations.get(port);
            //System.out.println("    " + name + " " + getPathString(mySet));
            for (Switch sw : mySet) {
                if (swSet.contains(sw) && !mySet.contains(ConfigParser.rootSwitch)) {
                    intersection.add(sw);
                }
            }
        }

        return intersection;
    }

    public void showPortRelations(String prefix) {
        for (String port : switchRelations.keySet()) {
            Set<Switch> swSet = switchRelations.get(port);
            System.out.println(prefix + " " + name + "."
                    + port + " " + getPathString(swSet));
        }
    }

    public String getPathString(Set<Switch> swSet) {
        String result = "";
        for (Switch sw : swSet) {
            result += sw.getName() + " ";
        }

        return result;
    }

    public Set<Switch> getIntersection(Set<Switch> s1) {
        return null;
    }

    public Map<String, Set<Switch>> getSwitchRelations() {
        return switchRelations;
    }

    public boolean areYouInList(Set<String> reqTargetMacs) {
        for (String reqMac : reqTargetMacs) {
            if (targetSet.contains(reqMac)) {
                return true;
            }
        }

        return false;
    }

    public Map<String, Node> getConnections() {
        return connections;
    }

    //
    public String getName() {
        return name;
    }

    public String getVendor() {
        return " ";
    }

    public String getModel() {
        return " ";
    }

    public String getMacAddress() {
        return " ";
    }

    public String getIpAddresses() {
        return " ";
    }

    public String getSpecificNotice() {
        return "ТРАНК";
    }

    // Implementation of Comparator interface
    public int compare(String pn1, String pn2) {
        Matcher um1 = portPattern.matcher(pn1);
        Matcher um2 = portPattern.matcher(pn2);

        if (!um1.matches()) {
            return -1;
        }

        if (!um2.matches()) {
            return 1;
        }

        int i11 = um1.start(1);
        int i12 = um2.start(1);
        String pr1 = pn1.substring(0, i11);
        String pr2 = pn2.substring(0, i12);

        int i1 = new Integer(um1.group(1)).intValue();
        int i2 = new Integer(um2.group(1)).intValue();
        
        if (pr1.equals(pr2))
            return i1 - i2;
        else {
            Collator c = Collator.getInstance();
            return c.compare(pr1, pr2);
        }
    }

    public boolean equals(Object c2) {
        return false;
    }

    // Private interface
    void addDataSources(String portName) throws IOException {
        File portDirFO = new File(jrobinDir, portName);
        if (!portDirFO.isDirectory()) {
            return;
        }

        File ifInOctets = new File(portDirFO, "ifInOctets.jrb");
        File ifOutOctets = new File(portDirFO, "ifOutOctets.jrb");
        File ifInErrors = new File(portDirFO, "ifInErrors.jrb");
        File ifOutErrors = new File(portDirFO, "ifOutErrors.jrb");
        File ifInUcastpkts = new File(portDirFO, "ifInUcastpkts.jrb");
        File ifOutUcastPkts = new File(portDirFO, "ifOutUcastPkts.jrb");
        File ifInDiscards = new File(portDirFO, "ifInDiscards.jrb");
        File ifOutDiscards = new File(portDirFO, "ifOutDiscards.jrb");

        System.out.println("=== Adding datasources for " + name + "."
                + portName);

        if (ifInOctets.exists()) {
            jrProcessor.addDatasource("ifInOctets_" + portName, ifInOctets.getCanonicalPath(),
                    "ifInOctets", "AVERAGE");
        }

        if (ifOutOctets.exists()) {
            jrProcessor.addDatasource("ifOutOctets_" + portName, ifOutOctets.getCanonicalPath(),
                    "ifOutOctets", "AVERAGE");
        }

        if (ifInErrors.exists()) {
            jrProcessor.addDatasource("ifInErrors_" + portName, ifInErrors.getCanonicalPath(),
                    "ifInErrors", "AVERAGE");
        }

        if (ifOutErrors.exists()) {
            jrProcessor.addDatasource("ifOutErrors_" + portName, ifOutErrors.getCanonicalPath(),
                    "ifOutErrors", "AVERAGE");
        }

        if (ifInDiscards.exists()) {
            jrProcessor.addDatasource("ifInDiscards_" + portName, ifInDiscards.getCanonicalPath(),
                    "ifInDiscards", "AVERAGE");
        }

        if (ifOutDiscards.exists()) {
            jrProcessor.addDatasource("ifOutDiscards_" + portName, ifOutDiscards.getCanonicalPath(),
                    "ifOutDiscards", "AVERAGE");
        }
    }

    double getOctetsPerSec(String portName, boolean out, String consol) {
        try {
            String agName = "if" + (out ? "Out" : "In") + "Octets_" + portName;
            return jrProcessor.getAggregate(agName, consol);
        } catch (RrdException exc) {
            return -1;
        }
    }

    double getErrorsPerSec(String portName, boolean out, String consol) {
        try {
            String agName = "if" + (out ? "Out" : "In") + "Errors_" + portName;
            return jrProcessor.getAggregate(agName, consol);
        } catch (RrdException exc) {
            return -1;
        }
    }

    double getDiscardsPerSec(String portName, boolean out, String consol) {
        try {
            String agName = "if" + (out ? "Out" : "In") + "Discards_" + portName;
            return jrProcessor.getAggregate(agName, consol);
        } catch (RrdException exc) {
            return -1;
        }
    }


    boolean isPortUp(String portName) {
        return upPorts.contains(portName);
    }


    int getPortSpeed(String portName) {
        return portSpeed.get(portName).intValue();
    }

    
    public String getSysDescr() {
        return sysDescr;
    }

    String getVlanString(String portName) {
        String result = "n/a";
        Set<Integer> vlans = portVlanMap.get(portName);
        if (vlans != null) {
            Set<Integer> sortedVlans = new TreeSet<Integer>(vlans);
            result = " ";
            for (Integer vid : sortedVlans) {
                result += vid.toString() + " ";
            }
        }

        return result;
    }
}
